package com.example.easyexcel.exports.simple_export;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.example.easyexcel.exports.customized.strategy.AutoHeadColumnWidthStyleStrategy;
import com.example.easyexcel.exports.customized.util.ExportUtil;
import com.example.easyexcel.exports.log.EasyExcelLogDto;
import com.example.easyexcel.exports.log.EasyLogService;
import com.example.easyexcel.exports.simple_export.dto.ExcelHead;
import com.example.easyexcel.exports.simple_export.dto.ExportConfig;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URLDecoder;
import java.util.*;

@Slf4j
@Component
@Aspect
public class SimpleExporterAop {
    @Autowired(required = false)
    EasyLogService easyExcelLogService;

    // 匹配所有持有指定注解的类里面的方法, 即要把注解加在类上. 在接口上声明不起作用
    @Around("@annotation(zhujie))")
    public Object around(ProceedingJoinPoint point, SimpleExporter zhujie) throws Throwable {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String bizData = request.getHeader("SIMPLE_EXPORT_JSON_PARAMS");
        
        if (StrUtil.isEmpty(bizData)) {
            return point.proceed();
        } else {
            String decode = URLDecoder.decode(bizData, "UTF-8");
            ExportConfig exportReqBody = JSONUtil.toBean(decode, ExportConfig.class);
            if (ExportConfig.loseParam(exportReqBody)) {
                throw new Exception("缺少导出配置");
            }
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            String methodInfo = point.getTarget().getClass().getSimpleName() + "." + method.getName() + "(...)";
            log.info("开始-通用导出，执行方法：" + methodInfo + "，导出配置：" + decode);

            setPageFieldPosition(method, exportReqBody);
            doExport(point, method, exportReqBody);
            log.info("结束-通用导出，执行方法：" + methodInfo);
            return null;
        }
    }

    private void setPageFieldPosition(Method method, ExportConfig exportReqBody) {
        Parameter[] parameters = method.getParameters();
        String pageNum = exportReqBody.getPageConfig().getFieldOfPageNum();
        String pageSize = exportReqBody.getPageConfig().getFieldOfPageSize();
        // 遍历参数，获取参数上的注解
//        for (Parameter parameter : parameters) {
//            Annotation[] annotations = parameter.getAnnotations();
//            for (Annotation annotation : annotations) {
//                System.out.println("Annotation on parameter: " + annotation);
//            }
//        }
        // 创建参数名称发现器
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        // 获取方法的参数名称
        String[] parameterNames = discoverer.getParameterNames(method);
        for (int i = 0; i < parameterNames.length; i++) {
            if (parameters[i].getAnnotation(RequestBody.class) != null) {
                if (ReflectUtil.hasField(parameters[i].getType(), pageNum)) {
                    exportReqBody.getPageConfig().setIndexOfPageNumField(i);
                }
                if (ReflectUtil.hasField(parameters[i].getType(), pageSize)) {
                    exportReqBody.getPageConfig().setIndexOfpageSizeField(i);
                    exportReqBody.getPageConfig().setPageParamsPosition(2);
                }
            } else {
                if (parameterNames[i].equals(pageNum)) {
                    exportReqBody.getPageConfig().setIndexOfPageNumField(i);
                }
                if (parameterNames[i].equals(pageSize)) {
                    exportReqBody.getPageConfig().setIndexOfpageSizeField(i);
                    exportReqBody.getPageConfig().setPageParamsPosition(1);
                }
            }
        }
    }

    /**
     * 处理导出逻辑
     */
    @SneakyThrows
    private void doExport(ProceedingJoinPoint point, Method method, ExportConfig exportReqBody) {
        String fileName = ExportUtil.createFileName(exportReqBody.getFileName());
        Object businessResult;
        int sheetIndex = 0;
        int sheetSize = exportReqBody.getSheetSize();
        int rowLenth = exportReqBody.getHeadList().size();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        ExportUtil.setExcelRespProp(response, fileName);
        @Cleanup("finish") ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).autoCloseStream(true).build();
        List<List<String>> headers = ExportConfig.convertHead(exportReqBody.getHeadList());

        if (StrUtil.isEmpty(exportReqBody.getPageConfig().getFieldOfPageSize())) {
            //没有分页，直接导出
            businessResult = this.getBusinessResult(point, method);
            log.debug("获取到的业务数据：" + JSONUtil.toJsonStr(businessResult));
            List<?> dbDatas = this.getDataList(businessResult, exportReqBody);
            List<List<?>> rowDatas = new ArrayList<>(sheetSize);
            for (int i = 1; i < dbDatas.size() + 1; i++) {
                Object data = dbDatas.get(i - 1);
                this.dataFill(rowDatas, exportReqBody, data, rowLenth);
                if (i % sheetSize == 0 || i == dbDatas.size()) {
                    WriteSheet writeSheet = EasyExcel.writerSheet(sheetIndex++, "Sheet" + sheetIndex)
                            .head(headers)
                            .registerWriteHandler(new AutoHeadColumnWidthStyleStrategy()).build();
                    excelWriter.write(rowDatas, writeSheet);
                    rowDatas.clear();
                }
            }
        } else {
            // 有分页，算总数再导出
            businessResult = this.getBusinessResult(point, method, exportReqBody, 1, 1);
            log.debug("获取到的业务数据：" + JSONUtil.toJsonStr(businessResult));
            Integer total = Integer.valueOf(BeanUtil.getProperty(businessResult, exportReqBody.getPageConfig().getFieldOfTotal()) + "");
            for (int i = 0; i < PageUtil.totalPage(total, sheetSize); i++) {
                businessResult = this.getBusinessResult(point, method, exportReqBody, i, sheetSize);
                log.debug("获取到的业务数据：" + JSONUtil.toJsonStr(businessResult));
                List<?> dataList = this.getDataList(businessResult, exportReqBody);
                List<List<?>> rowDatas = new ArrayList<>(sheetSize);
                for (Object data : dataList) {
                    this.dataFill(rowDatas, exportReqBody, data, rowLenth);
                }
                WriteSheet writeSheet = EasyExcel.writerSheet(sheetIndex++, "Sheet" + sheetIndex)
                        .head(headers)
                        .registerWriteHandler(new AutoHeadColumnWidthStyleStrategy()).build();
                excelWriter.write(rowDatas, writeSheet);
            }
        }

        // 操作日志
        EasyExcelLogDto easyExcelLogDto = new EasyExcelLogDto().setModule("demo").setFileName(fileName);
        if(null!= easyExcelLogService && easyExcelLogDto.isNotEmpty()){
            easyExcelLogService.exportLog(easyExcelLogDto);
        }
    }

    /**
     * 数据填充
     */
    private void dataFill(List<List<?>> rowDatas, ExportConfig exportReqBody, Object data, Integer size) {
        Object tmpData;
        Map<String, Object> rowDataMap = new HashMap<>(size);
        for (ExcelHead excelHead : exportReqBody.getHeadList()) {
            tmpData = this.getDataValue(data, excelHead.getFieldName());
            tmpData = excelHead.fillDefault(tmpData);
            rowDataMap.put(excelHead.getFieldName(), tmpData);
        }
        this.dataBeautify(rowDataMap, exportReqBody, data);
        ArrayList<Object> row = new ArrayList<>(size);
        for (ExcelHead excelHead : exportReqBody.getHeadList()) {
            row.add(rowDataMap.get(excelHead.getFieldName()));
        }
        rowDatas.add(row);
    }

    private Object getDataValue(Object data, String path) {// aa.bb.$cc.dd
        if (data == null) {
            return null;
        }
        String f1, f2;
        int index = path.indexOf(".");
        if (index > 0) {
            f1 = path.substring(0, index);
            f2 = path.substring(index + 1);
            data = getFieldValue(data, f1);
            if (data instanceof String && JSONUtil.isTypeJSON( (String) data) && f2.contains(".")) {
                return getDataValue(JSONUtil.parse(data), f2);
            } else {
                return getDataValue(data, f2);
            }
        } else {
            return getFieldValue(data, path);
        }
    }

    private Object getFieldValue(Object data, String path) {
        Object obj;
        try {
            obj = BeanUtil.getProperty(data, path);
        } catch (Exception e) {
            obj = JSONUtil.getByPath(JSONUtil.parse(data), path);
        }
        if (NumberUtil.isNumber(obj + "")) {
            obj = obj.toString();
        }
        return obj;
    }

    /**
     * 数据美化
     */
    private void dataBeautify(Map<String, Object> rowDataMap, ExportConfig exportReqBody, Object data) {
        // 格式转换
        if (CollUtil.isNotEmpty(exportReqBody.getValueFormats())) {
            for (Map.Entry<String, String> entity : exportReqBody.getValueFormats().entrySet()) {
                Object val = rowDataMap.get(entity.getKey());
                if (val != null && val instanceof Date) {
                    rowDataMap.put(entity.getKey(), DateUtil.format((Date) val, entity.getValue()));
                }
            }
        }
        // 字段需要进行转义
        if (CollUtil.isNotEmpty(exportReqBody.getValueMatchRules())) {
            for (Map.Entry<String, Map<String, String>> matcher : exportReqBody.getValueMatchRules().entrySet()) {
                Object val = rowDataMap.get(matcher.getKey());
                if (val != null) {
                    for (Map.Entry<String, String> role : matcher.getValue().entrySet()) {
                        if (StrUtil.equals(role.getKey(), val.toString())) {
                            rowDataMap.put(matcher.getKey(), role.getValue());
                        }
                    }
                }
            }
        }
        // 字段由多个值拼接而成
        if (CollUtil.isNotEmpty(exportReqBody.getValueConcatRules())) {
            exportReqBody.getValueConcatRules().entrySet().forEach(entity -> {
                String[] arr = entity.getValue();
                if (arr != null) {
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < arr.length; i++) {
                        String tmp = arr[i];
                        if (tmp.startsWith("@")) {
                            tmp = tmp.substring(1);
                            Object val;
                            if (rowDataMap.containsKey(tmp)) {
                                val = rowDataMap.get(tmp);
                                sb.append(val != null ? val + "" : "");
                            } else {
                                val = this.getFieldValue(data, tmp);
                                sb.append(val != null ? val + "" : "");
                            }
                        } else {
                            sb.append(arr[i]);
                        }
                    }
                    rowDataMap.put(entity.getKey(), sb.toString());
                }
            });

        }
    }

    /**
     * 分页用
     */
    private Object getBusinessResult(ProceedingJoinPoint point, Method method, ExportConfig exportReqBody, Integer pageNum, Integer pageSize) {
        Object[] args = point.getArgs();
        if (exportReqBody.getPageConfig().getPageParamsPosition() == 2) {
            ReflectUtil.setFieldValue(args[exportReqBody.getPageConfig().getIndexOfPageNumField()], exportReqBody.getPageConfig().getFieldOfPageNum(), pageNum);
            ReflectUtil.setFieldValue(args[exportReqBody.getPageConfig().getIndexOfpageSizeField()], exportReqBody.getPageConfig().getFieldOfPageSize(), pageSize);
        } else {
            args[exportReqBody.getPageConfig().getIndexOfPageNumField()] = pageNum;
            args[exportReqBody.getPageConfig().getIndexOfpageSizeField()] = pageSize;
        }
        Object data = getBusinessResult(point, method);
        return data;
    }

    /**
     * 无分页用
     */
    private Object getBusinessResult(ProceedingJoinPoint point, Method method) {
        Object data = ReflectUtil.invoke(point.getTarget(), method, point.getArgs());
        return data;
    }

    /**
     * 获取数据集
     */
    private List<?> getDataList(Object serviceResults, ExportConfig exportReqBody) {
        if (StrUtil.isEmpty(exportReqBody.getFieldOfDataResult())) {
            return (List<?>) serviceResults;
        } else {
            return BeanUtil.getProperty(serviceResults, exportReqBody.getFieldOfDataResult());
        }
    }


}